feat(ci): add /assign GitHub Action with stale cleanup (Fixes #2630)#2634
feat(ci): add /assign GitHub Action with stale cleanup (Fixes #2630)#2634Ayush7614 wants to merge 5 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds ChangesIssue Assignment Automation
Estimated code review effort: 5 (Critical) | ~120 minutes Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
📋 Issue PlannerBuilt with CodeRabbit's Coding Plans for faster development and fewer bugs. View plan used: ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
LLxprt PR Review – PR #2634Issue AlignmentImplements the Side Effects
Code Quality
Tests and Coverage
VerdictReady — implementation matches issue requirements, side effects are bounded and documented, code quality is high with defensive concurrency handling, and test coverage materially increases with real-behavior assertions rather than mock theater. |
OpenCodeReview — PR #2634
Findings without a resolvable position
|
|
@Ayush7614 run shellcheck |
|
@CodeRabbit is the shell necessary in here or could this be done entirely in yaml? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '== Candidate files =='
fd -a '^(assign\.yml|assign-issue\.sh|assign-stale-cleanup\.yml|unassign-stale-issues\.sh|assign-workflow\.test\.js)$' . \
-E node_modules -E dist -E build
printf '%s\n' '== Workflow outlines =='
for f in $(fd -a '^(assign\.yml|assign-stale-cleanup\.yml)$' . -E node_modules -E dist -E build); do
echo "--- $f"
nl -ba "$f"
done
printf '%s\n' '== Script function outlines =='
for f in $(fd -a '^(assign-issue\.sh|unassign-stale-issues\.sh)$' . -E node_modules -E dist -E build); do
echo "--- $f"
rg -n '^(#!/|[[:alpha:]_][[:alnum:]_]*\(\)|[[:alpha:]_][[:alnum:]_]*\(\s*\{)|\b(gh|jq|curl)\b|case |if |while |for )' "$f" || true
doneLength of output: 984 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '== Repository change summary =='
git status --short
git diff --name-status HEAD~1..HEAD 2>/dev/null || true
printf '%s\n' '== /assign implementation references =='
rg -n -i -C 3 '/assign|auto-assigned|stale cleanup|unassign.*stale' \
-g '*.yml' -g '*.yaml' -g '*.sh' -g 'assign-workflow.test.js' . \
-g '!node_modules/**' -g '!dist/**' -g '!build/**' || true
printf '%s\n' '== Contract test =='
test_file="$(fd -a '^assign-workflow\.test\.js$' scripts/tests | head -n 1)"
if [ -n "$test_file" ]; then
nl -ba "$test_file"
fiLength of output: 8443 🏁 Script executed: #!/bin/bash
set -euo pipefail
for f in \
.github/workflows/assign.yml \
.github/workflows/assign-stale-cleanup.yml \
.github/scripts/assign-issue.sh \
.github/scripts/unassign-stale-issues.sh
do
if [ -f "$f" ]; then
printf '\n%s\n' "===== $f ====="
awk '{ printf "%4d %s\n", NR, $0 }' "$f"
else
printf '%s\n' "MISSING: $f"
fi
doneLength of output: 20136
YAML is appropriate for the triggers, exact-comment gate, permissions, concurrency, and scheduling. But the implementation also needs dynamic GitHub API work and control flow:
Those require imperative code. The current checked-in shell scripts are a reasonable choice, particularly since the logic is independently contract-tested. The alternative would be |
|
@CodeRabbit is there an attack vector here and if so can we limit it? thinking like 1000 bots smash us with /assign ? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
workflow="$(fd -a '^assign\.yml$' . -E node_modules -E dist -E build | head -n1)"
script="$(fd -a '^assign-issue\.sh$' . -E node_modules -E dist -E build | head -n1)"
test_file="$(fd -a '^assign-workflow\.test\.js$' . -E node_modules -E dist -E build | head -n1)"
printf '%s\n' '== Candidate paths =='
printf 'workflow=%s\nscript=%s\ntest=%s\n' "$workflow" "$script" "$test_file"
for file in "$workflow" "$script" "$test_file"; do
if [ -n "$file" ] && [ -f "$file" ]; then
printf '\n===== %s =====\n' "$file"
nl -ba "$file"
fi
doneLength of output: 1095 🏁 Script executed: #!/bin/bash
set -euo pipefail
find . \
-path '*/node_modules' -prune -o \
-path '*/dist' -prune -o \
-path '*/build' -prune -o \
-type f \( \
-name 'assign.yml' -o \
-name 'assign-issue.sh' -o \
-name 'assign-workflow.test.js' \
\) -print |
while IFS= read -r file; do
printf '\n===== %s =====\n' "$file"
sed -n '=' "$file" | sed 'N;s/\n/\t/'
doneLength of output: 1959 🏁 Script executed: #!/bin/bash
set -euo pipefail
for file in \
.github/workflows/assign.yml \
.github/scripts/assign-issue.sh \
scripts/tests/assign-workflow.test.js
do
if [ -f "$file" ]; then
printf '\n===== %s =====\n' "$file"
sed -n '1,260p' "$file"
else
printf 'MISSING: %s\n' "$file"
fi
doneLength of output: 14287
I would limit this in two layers:
Also, for failed eligibility, I would avoid posting/updating sticky feedback for every request—or only provide that feedback for known contributors. Otherwise attackers can deliberately generate persistent issue-comment noise and write API traffic. The three-assignment cap protects issue ownership; it does not rate-limit invocation volume. I recommend treating global concurrency plus early rejection as required hardening before enabling this on a public repository. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/scripts/assign-issue.sh:
- Around line 95-137: Update the workflow permissions associated with the
assign-issue script to include pull-requests: read alongside the existing
permissions. Ensure the gh search prs invocation in the merged_pr_count
eligibility check can read pull requests and preserve the current eligibility
behavior.
In @.github/scripts/unassign-stale-issues.sh:
- Around line 148-166: Update the assignee evaluation in the function containing
the exemption loop so the `EXEMPT_LOGIN` check occurs inside the per-assignee
loop. Skip only `acoliver` while continuing to evaluate other co-assignees
through `assignment_age_ok_to_unassign`; remove the issue-level `return 0`
behavior and preserve the existing unassignment decision flow.
In @.github/workflows/assign.yml:
- Around line 11-13: Update the workflow permissions block in assign.yml to add
pull-requests: read alongside contents: read and issues: write, allowing the
merged pull-request search used by the workflow to function correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 1f3d8a5f-64b7-4b6f-a790-aa65ed95e103
📒 Files selected for processing (6)
.github/scripts/assign-issue.sh.github/scripts/unassign-stale-issues.sh.github/workflows/assign-stale-cleanup.yml.github/workflows/assign.ymlCONTRIBUTING.mdscripts/tests/assign-workflow.test.js
|
@Ayush7614 look at #2634 (comment) and the other coderabbit reviews. LLet's assume at some point someone will comment /assign on everything so let's add a cap for how many issues they can be assigned at once, the concurrency cap. ask your agent for any other hardening recommendations to prevent also spammers (github name BuyMyStupidThing /assign every issue it can) |
|
@acoliver addressed shellcheck + the spam-hardening notes (and CodeRabbit’s actionable items): CI / shellcheck
Spam / flood hardening
CodeRabbit
Happy to add more rate-limiting (e.g. daily attempt budget) if you want that next. |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.github/scripts/assign-issue.sh (2)
87-133: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDo not grant self-assignment eligibility from repository roles or the trusted file.
The eligibility block accepts
OWNER,MEMBER,COLLABORATOR, and.github/trusted-contributors.txtmembership without a merged PR or prior issue assignment, while the failure feedback only lists those two conditions plus the trusted file. Remove the role bypass or document and test the role/trusted-file exceptions as intentional policy.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/scripts/assign-issue.sh around lines 87 - 133, Restrict self-assignment eligibility in the eligibility logic around merged_pr_count and prior_assigned_count to users with a merged PR or prior issue assignment. Remove the TRUSTED_FILE membership check and the AUTHOR_ASSOCIATION role bypass, including their eligibility reasons, so OWNER, MEMBER, COLLABORATOR, and trusted-file entries cannot bypass those requirements.
137-169: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftMake assignment and tracking-label updates a single failure domain.
If
gh issue edit --add-labelfails, or the run is canceled between the assignment and label write, the issue stays assigned withoutauto-assigned. The stale cleanup workflow selects open issues bylabel:auto-assigned, so this leave-behind assignment will not be discovered and may run against the per-user cap. Use one API update that sets the assignee and preserves existing labels, and report failure unless both states persist before printing success.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/scripts/assign-issue.sh around lines 137 - 169, The assignment and auto-assigned label writes in the post-eligibility flow must be one atomic tracking update. Replace the separate gh issue edit calls around the assignment verification and label addition with a single API update that sets USER_LOGIN as assignee while preserving all existing labels, then re-read the issue and report failure unless both the assignee and AUTO_ASSIGNED_LABEL are present; only print success after both states persist.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/scripts/assign-issue.sh:
- Around line 39-43: Update the existing_id lookup in the issue-assignment flow
to capture gh api output and its exit status separately, rather than masking
failures with || true. Fail closed when the API request returns non-zero, while
preserving the current behavior when the request succeeds but no comment matches
MARKER.
- Around line 40-42: Update the comment lookup in the assignment feedback flow
to match the marker only when the comment was authored by the configured bot
account, preventing user-authored marker copies from being patched. Preserve
pagination and first-match behavior, and use the existing bot identity
configuration rather than hardcoding an unrelated author.
- Around line 60-68: Serialize the entire assignment check-and-mutation flow in
the `/assign` workflow per issue, rather than per commenter, by updating the
concurrency group in the workflow configuration and setting cancel-in-progress
to false. Ensure the existing zero-assignee check and add-assignee operation in
the assignment script execute within that shared per-issue queue.
- Around line 60-63: Update the assignee count read in the assign guard to fail
closed: remove the fallback that converts gh issue view errors into 0, capture
or propagate the command failure, and abort with clear feedback when reading
current assignees fails. Continue to the assignment flow only when
assignee_count is successfully obtained.
---
Outside diff comments:
In @.github/scripts/assign-issue.sh:
- Around line 87-133: Restrict self-assignment eligibility in the eligibility
logic around merged_pr_count and prior_assigned_count to users with a merged PR
or prior issue assignment. Remove the TRUSTED_FILE membership check and the
AUTHOR_ASSOCIATION role bypass, including their eligibility reasons, so OWNER,
MEMBER, COLLABORATOR, and trusted-file entries cannot bypass those requirements.
- Around line 137-169: The assignment and auto-assigned label writes in the
post-eligibility flow must be one atomic tracking update. Replace the separate
gh issue edit calls around the assignment verification and label addition with a
single API update that sets USER_LOGIN as assignee while preserving all existing
labels, then re-read the issue and report failure unless both the assignee and
AUTO_ASSIGNED_LABEL are present; only print success after both states persist.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 472bf4a3-4bb3-470f-8835-e583148e1084
📒 Files selected for processing (6)
.github/scripts/assign-issue.sh.github/scripts/unassign-stale-issues.sh.github/workflows/assign-stale-cleanup.yml.github/workflows/assign.ymlCONTRIBUTING.mdscripts/tests/assign-workflow.test.js
🚧 Files skipped from review as they are similar to previous changes (5)
- .github/workflows/assign-stale-cleanup.yml
- .github/workflows/assign.yml
- CONTRIBUTING.md
- scripts/tests/assign-workflow.test.js
- .github/scripts/unassign-stale-issues.sh
Implement the documented self-assignment slash command and a scheduled cleanup for auto-assigned issues with no PR activity for two weeks. Fixes vybestack#2630.
Satisfy shellcheck, add pull-requests:read, reject Bot actors, serialize per-actor concurrency, keep the 3-issue cap, and unassign stale co-assignees without skipping whole issues for acoliver.
27cb386 to
0204988
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
scripts/tests/assign-remediation3.test.js (1)
48-82: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
makeLabeledEventandmakeUnlabeledEventare re-implemented here but already exist inassign-helpers.js. Importing them would avoid drift between the two definitions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/tests/assign-remediation3.test.js` around lines 48 - 82, Remove the duplicate makeLabeledEvent and makeUnlabeledEvent definitions from this test and import both helpers from assign-helpers.js. Update any references as needed while preserving the existing test behavior and arguments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/scripts/assign-issue.sh:
- Around line 392-405: Update verified_rollback_and_fail and its non-contention
callers so feedback_msg is posted after a successful rollback for API-failure
and mutation-cap paths, preserving the existing exit codes. Keep successful
contention-loser rollbacks silent to avoid overwriting the winner’s sticky
feedback, while retaining the current rollback-failure handling.
---
Nitpick comments:
In `@scripts/tests/assign-remediation3.test.js`:
- Around line 48-82: Remove the duplicate makeLabeledEvent and
makeUnlabeledEvent definitions from this test and import both helpers from
assign-helpers.js. Update any references as needed while preserving the existing
test behavior and arguments.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 32eeab8c-2569-420f-844e-9508436186dd
📒 Files selected for processing (18)
.github/assignment-history.txt.github/scripts/assign-issue.sh.github/scripts/record-assignment-history.sh.github/scripts/unassign-stale-issues.sh.github/workflows/assign-stale-cleanup.yml.github/workflows/assign.ymlCONTRIBUTING.mdscripts/tests/assign-helpers.jsscripts/tests/assign-remediation.test.jsscripts/tests/assign-remediation2.test.jsscripts/tests/assign-remediation3.test.jsscripts/tests/assign-remediation4.test.jsscripts/tests/assign-remediation5.test.jsscripts/tests/assign-remediation6.test.jsscripts/tests/assign-remediation7.test.jsscripts/tests/assign-workflow-behaviors.test.jsscripts/tests/assign-workflow.test.jsscripts/tests/fake-gh.py
🚧 Files skipped from review as they are similar to previous changes (2)
- .github/workflows/assign-stale-cleanup.yml
- CONTRIBUTING.md
| validate_github_login() { | ||
| local login="$1" | ||
| [[ -n "${login}" ]] || return 1 | ||
| [[ "${#login}" -le 39 ]] || return 1 | ||
| [[ "${login}" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?$ ]] || return 1 | ||
| } |
There was a problem hiding this comment.
The regex in
validate_github_loginallows consecutive hyphens (e.g.,user--name). GitHub usernames require each hyphen to be surrounded by alphanumeric characters. Consider updating the pattern to^[A-Za-z0-9]+(-[A-Za-z0-9]+)*$, which enforces non-empty segments separated by single hyphens and naturally prevents leading, trailing, or consecutive hyphens.
| validate_github_login() { | ||
| local login="$1" | ||
| [[ -n "${login}" ]] || return 1 | ||
| [[ "${#login}" -le 39 ]] || return 1 | ||
| [[ "${login}" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?$ ]] || return 1 | ||
| } |
There was a problem hiding this comment.
No test coverage was added for the new
validate_github_loginfunction. This validation logic contains subtle boundary conditions (empty input, max length, single-character names, leading/trailing/consecutive hyphens) that should be covered by unit tests to prevent regressions.
| 1) | ||
| printf '‼ Label %q exists with conflicting definition\n' "${LABEL_NAME}" >&2 | ||
| exit 1 | ||
| ;; |
There was a problem hiding this comment.
Misleading error message when POST fails:
validate_history_labelconflates 'absent' and 'collision' by returning 1 for both cases. If the POST fails for transient reasons (network error, rate limit, permissions) and the label is still absent, this script incorrectly reportsLabel %q exists with conflicting definitioninstead of indicating the creation actually failed. Consider splitting the return codes (e.g., return 2 for collision, matchingvalidate_label_definitioninassign-issue.sh) so the post-POST check can distinguish these cases.
| if ! gh api --method POST "repos/${REPO}/labels" \ | ||
| -f name="${LABEL_NAME}" \ | ||
| -f color="${HISTORY_COLOR}" \ | ||
| -f description="${HISTORY_DESC}" \ | ||
| --silent >/dev/null 2>&1; then |
There was a problem hiding this comment.
The POST request to create the label uses
--silent >/dev/null 2>&1, which completely discards the actual GitHub API error message. If creation fails for reasons other than concurrent creation (e.g., invalid color format, insufficient permissions, rate limiting), the script only reports a generic failure without the underlying cause, making debugging difficult. Consider capturing stderr on failure to include the actual API error in the diagnostic output.
|
|
||
| # Source shared constants (history-label policy values + login validation). | ||
| # shellcheck source-path=SCRIPTDIR | ||
| source "$(dirname "${BASH_SOURCE[0]}")/assign-constants.sh" |
There was a problem hiding this comment.
The script does not validate that required constants (
HISTORY_PREFIX,HISTORY_COLOR,HISTORY_DESC) fromassign-constants.share set and valid before use. If any constant is missing or empty, the script will construct an invalid label name or send an invalid API request, leading to unpredictable failures. Add explicit checks (e.g.,: "${HISTORY_PREFIX:?Missing HISTORY_PREFIX}") after sourcing the constants file.
| validate_array_field(body, "assignees") | ||
| validate_array_field(body, "labels") | ||
| if "assignees" in body: | ||
| issue["_assignees"] = list(body["assignees"]) |
There was a problem hiding this comment.
handle_patchfor/issues/{number}does not filter out unassignable logins, unlikehandle_postfor/issues/{number}/assigneeswhich silently drops them. The module docstring documents "silent dropping of configured unassignable logins", and the POST handler implements this at lines 877-880, but the PATCH handler assignsbody["assignees"]directly at line 964 without this check. This inconsistency means PATCH requests can add unassignable users to issues, breaking tests that rely on unassignable login behavior and diverging from the documented behavior.
| # GitHub defaults omitted state to "open" for /issues listing. | ||
| state_filter = params.get("state", "open") | ||
| assignee_filter = params.get("assignee") | ||
| label_filter = params.get("labels") | ||
| for _num_str, issue in state.get("issues", {}).items(): | ||
| if state_filter == "open" and issue.get("state", "open") != "open": | ||
| continue | ||
| if state_filter == "closed" and issue.get("state") != "closed": | ||
| continue | ||
| if state_filter == "all": | ||
| pass |
There was a problem hiding this comment.
Invalid
statefilter values in both/issueslisting andsearch/issuesare silently accepted and fall through without filtering, causing all items to be returned regardless of their actual state. For example,state=invalidorstate:invalidbypasses the open/closed filtering logic entirely because onlyopen,closed, andallare recognized. This could lead to incorrect test assertions when malformed or unexpected state parameters are passed.
| # Final type filter | ||
| if item_type == "pr": | ||
| results = [r for r in results if r.get("pull_request") is not None] | ||
| elif item_type == "issue": | ||
| results = [r for r in results if r.get("pull_request") is None] |
There was a problem hiding this comment.
Invalid
type:oris:qualifier values insearch/issues(e.g.type:invalid,is:invalid) are silently ignored, causing both issues and PRs to be returned instead of rejecting the query or returning empty results. Fortype:invalid, the final type filter at lines 810-813 does not match, so both issues and PRs are included. Foris:invalid, the unknown qualifier is silently dropped, which may mask bugs in search query construction in the scripts under test.
| if body and isinstance(body, list): | ||
| label_names = body | ||
| elif body and "labels" in body and isinstance(body["labels"], list): | ||
| label_names = body["labels"] | ||
| else: | ||
| label_names = [] |
There was a problem hiding this comment.
In
handle_postfor/issues/{number}/labels(lines 846-851) and/issues/{number}/assignees(lines 871-876), if the request body contains alabelsorassigneesfield that is not a list (e.g. a string), the fake silently falls back to an empty list instead of returning a validation error. This is inconsistent withvalidate_array_field, which exists to reject non-list array fields, and with the documented behavior that string-typed array fields are rejected. Malformed request bodies should be rejected rather than silently ignored to avoid masking bugs in the scripts under test.
| if body and "assignees" in body and isinstance(body["assignees"], list): | ||
| logins = body["assignees"] | ||
| elif body and isinstance(body, list): | ||
| logins = body | ||
| else: | ||
| logins = [] |
There was a problem hiding this comment.
In
handle_postfor/issues/{number}/assignees(lines 871-876), if the request body contains anassigneesfield that is not a list (e.g. a string), the fake silently falls back to an empty list instead of returning a validation error. This is inconsistent withvalidate_array_field, which exists to reject non-list array fields, and with the documented behavior that string-typed array fields are rejected. Malformed request bodies should be rejected rather than silently ignored to avoid masking bugs in the scripts under test.
Summary
/assignself-assignment workflow (assign.yml+assign-issue.sh): exact-match comment gate, eligibility (merged PR / prior assignment / trusted contributors / owner-member-collaborator), 3-issue cap,auto-assignedlabel, and sticky feedback when assignment succeeds or silently fails.assign-stale-cleanup.yml+unassign-stale-issues.sh) to unassign auto-assigned issues with no linked PR activity for 14 days (exemptsacoliver).CONTRIBUTING.mdand adds YAML/script contract tests inscripts/tests/assign-workflow.test.js.Fixes #2630.
Test plan
bash -non both scriptsbunx vitest run --config scripts/tests/vitest.config.ts scripts/tests/assign-workflow.test.js(11/11 passed)/assignon an unassigned eligible issue and confirm assignment + sticky feedback +auto-assignedlabel/assignon an already-assigned issue and confirm refusal feedbackAssign Stale Cleanupviaworkflow_dispatchafter labeling a stale test issueSummary by CodeRabbit
/assign, with strict eligibility checks and an assignment cap./assign, including eligibility, limits, feedback outcomes, and stale cleanup timing.